package com.auditlog.datasource.execute;

import com.auditlog.datasource.ConnectionProxy;
import com.auditlog.datasource.StatementProxy;
import com.auditlog.datasource.context.ConnectionContext;
import com.auditlog.datasource.context.ContextHolder;
import com.auditlog.datasource.struct.SqlMeta;
import com.google.common.base.Stopwatch;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.sql.SQLException;
import java.sql.Statement;

@Slf4j
@Data
public abstract class BaseExecutor<T, S extends Statement> implements Executor<T> {

    private StatementProxy<S> statementProxy;
    private StatementCallback<T, S> statementCallback;
    private SqlMeta sqlMeta;

    public BaseExecutor(StatementProxy<S> statementProxy, StatementCallback<T, S> statementCallback, SqlMeta sqlMeta) {
        this.statementCallback = statementCallback;
        this.statementProxy = statementProxy;
        this.sqlMeta = sqlMeta;
    }

    public T doExecute(Object... args) throws SQLException {
        SQLException exception = null;
        T result;
        beforeExecute();
        try {
            result = this.getStatementCallback().execute(this.getStatementProxy().getTargetStatement(), args);
        } catch (SQLException sqlException) {
            exception = sqlException;
            ConnectionContext connectionContext = ContextHolder.getConnectionContext();
            connectionContext.setTargetCause(exception);
            connectionContext.setTargetError(true);
            throw exception;
        } finally {
            if (exception == null) {
                afterExecute();
            } else {
                ConnectionContext connectionContext = ContextHolder.getConnectionContext();
                if (connectionContext.isChangeAutoCommit() && connectionContext.isMultiOrBatch()) {
                    // 防止有的批处理是自动提交事务，更新成功了部分数据而没有记录到日志
                    afterExecute();
                }
            }
        }
        return result;
    }

    @Override
    public T execute(Object... args) throws Throwable {
        ConnectionProxy connectionProxy = this.statementProxy.getConnectionProxy();
        Stopwatch stopwatch = Stopwatch.createStarted();
        String targetSQL = this.statementProxy.getTargetSQL();
        boolean changeAutoCommit = false;
        try {
            if (connectionProxy.getAutoCommit()) {
                connectionProxy.setAutoCommit(false);
                changeAutoCommit = true;
                ContextHolder.getConnectionContext().setChangeAutoCommit(true);
            }
            return doExecute(args);
        } finally {
            long elapse = stopwatch.stop().elapsed().toNanos();
            ContextHolder.getExecuteContext().getSqlStatistician().collect(targetSQL, elapse);
            ContextHolder.getConnectionContext().reset();
            if (changeAutoCommit) {
                connectionProxy.setAutoCommit(true);
            }
        }
    }

    protected void handleException(ConnectionContext connectionContext, Exception e) throws SQLException {
        connectionContext.setError(true);
        connectionContext.setCause(e);
        if (!connectionContext.isOnExceptionContinue()) {
            if (!(e instanceof SQLException)) {
                throw new SQLException(e);
            }
            throw (SQLException) e;
        }
    }

    public abstract void beforeExecute() throws SQLException;

    public abstract void afterExecute() throws SQLException;
}
